package com.googlecode.jgae.dao.objectify;

import com.googlecode.jgae.dao.DAO;
import com.googlecode.jgae.domain.BaseDomain;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.NotFoundException;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyService;
import com.googlecode.objectify.cmd.Query;

import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Denis Migol
 */
public class ObjectifyDAO<ID extends Serializable, E extends BaseDomain<ID>> implements DAO<ID, E> {
    protected final Objectify ofy;
    protected final Class<E> entityClass;

    protected ObjectifyDAO(Class<E> entityClass) {
        this(ObjectifyService.ofy(), entityClass);
    }

    protected ObjectifyDAO(Objectify ofy, Class<E> entityClass) {
        ofy.getFactory().register(entityClass);
        this.ofy = ofy;
        this.entityClass = entityClass;
    }

    @Override
    public E createNew() {
        try {
            return entityClass.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int count() {
        return ofy.load().type(entityClass).count();
    }

    @Override
    public List<E> query(int offset, int limit) {
        Query<E> query = this.ofy.load().type(entityClass);
        if (limit >= 0) {
            query.limit(limit);
        }
        // TODO this is expensive ($$$)
        query.offset(offset);
        return query.list();
    }

    @Override
    public List<Key<E>> queryKeys(int offset, int limit) {
        Query<E> query = this.ofy.load().type(entityClass);
        if (limit >= 0) {
            query.limit(limit);
        }
        // TODO this is expensive ($$$)
        query.offset(offset);
        return query.keys().list();
    }

    @Override
    public E load(ID id) {
        try {
            if (id instanceof Long) {
                return ofy.load().type(entityClass).id((Long) id).safeGet();
            } else if (id instanceof String) {
                return ofy.load().type(entityClass).id((String) id).safeGet();
            }
        } catch (NotFoundException ignore) {
        }
        return null;
    }

    @Override
    public Map<ID, E> load(Iterable<ID> ids) {
        if (ids != null) {
            return ofy.load().type(entityClass).ids(ids);
        } else {
            return Collections.emptyMap();
        }
    }

    @Override
    public ID save(E entity) {
        return toId(ofy.save().entity(entity).now());
    }

    @Override
    public Map<ID, E> save(Iterable<E> entities) {
        if (entities != null) {
            return toIds(ofy.save().entities(entities).now());
        } else {
            return Collections.emptyMap();
        }
    }

    @Override
    public void delete(E entity) {
        if (entity != null) {
            ofy.delete().entity(entity).now();
        }
    }

    @Override
    public void delete(ID id) {
        if (id instanceof Long) {
            ofy.delete().type(entityClass).id((Long) id).now();
        } else if (id instanceof String) {
            ofy.delete().type(entityClass).id((String) id).now();
        }
    }

    @Override
    public void delete(Iterable<E> entities) {
        if (entities != null) {
            ofy.delete().entities(entities).now();
        }
    }

    @Override
    public void deleteIds(Iterable<ID> ids) {
        if (ids != null) {
            ofy.delete().type(entityClass).ids(ids).now();
        }
    }

    //
    // utils
    //

    protected final ID toId(Key<E> key) {
//        if (key == null) {
//            return null;
//        } else
        if (key.getId() == 0) {
            return (ID) key.getName();
        } else {
            return (ID) ((Long) key.getId());
        }
    }

    protected final Map<ID, E> toIds(Map<Key<E>, E> keys) {
        Map<ID, E> ret = new LinkedHashMap<ID, E>();
        for (Map.Entry<Key<E>, E> entry : keys.entrySet()) {
            ret.put(toId(entry.getKey()), entry.getValue());
        }
        return ret;
    }
}
